本文同步更新於blog
情境:這是一間國際婚禮公司
<?php
namespace App\VisitorPattern\Wedding;
class Program
{
/**
* @param string $weddingType
* @return string
*/
public function getWedding($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
'新郎:中式囍袍
新郎:黑色秀禾鞋
新娘:龍鳳褂
新娘:紅色秀禾鞋
';
break;
case 'Japanese':
echo
'新郎:繡有家紋的和服
新郎:雪駄
新娘:純潔的白無垢
新娘:草履
';
break;
}
}
}
熟稔設計模式的我們,一眼就看出來改寫的方向。
讓我們抽出新郎與新娘!
需求一:抽出新郎 (BrideGroom) 與新娘 (Bride) 類別
<?php
namespace App\VisitorPattern\Wedding;
class BrideGroom
{
/**
* @param string $weddingType
*/
public function getClothes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新郎:中式囍袍\n";
break;
case 'Japanese':
echo
"新郎:繡有家紋的和服\n";
break;
}
}
/**
* @param string $weddingType
*/
public function getShoes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新郎:黑色秀禾鞋\n";
break;
case 'Japanese':
echo
"新郎:雪駄\n";
break;
}
}
}
<?php
namespace App\VisitorPattern\Wedding;
class Bride
{
/**
* @param string $weddingType
*/
public function getClothes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新娘:龍鳳褂\n";
break;
case 'Japanese':
echo
"新娘:純潔的白無垢\n";
break;
}
}
/**
* @param string $weddingType
*/
public function getShoes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新娘:紅色秀禾鞋\n";
break;
case 'Japanese':
echo
"新娘:草履\n";
break;
}
}
}
<?php
namespace App\VisitorPattern\Wedding;
class Program
{
/**
* @param string $weddingType
*/
public function getWedding($weddingType)
{
$brideGroom = new BrideGroom();
$bride = new Bride();
$brideGroom->getClothes($weddingType);
$brideGroom->getShoes($weddingType);
$bride->getClothes($weddingType);
$bride->getShoes($weddingType);
}
}
正當我們得意洋洋之時,老闆說了一個令人震驚的需求。
Boss:「隨著版圖擴張,我們之後要支援印度、烏克蘭等各國的婚禮服裝。」
經過觀察我們可以發現,不過是哪一國的婚禮,
主角皆是新郎與新娘,且都需要取得服裝與鞋子。
其資料結構是穩定的。
變動的是服裝與鞋子的操作。
讓我們用訪問者模式改寫它!
需求二:配合版圖的擴張,實作訪問者模式
<?php
namespace App\VisitorPattern\Wedding\Contracts;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
interface WeddingRole
{
/**
* @param WeddingType $weddingType
*/
public function getClothes($weddingType);
/**
* @param WeddingType $weddingType
*/
public function getShoes($weddingType);
}
<?php
namespace App\VisitorPattern\Wedding\Contracts;
interface WeddingType
{
/**
* @param WeddingRole $role
*/
public function getClothes($role);
/**
* @param WeddingRole $role
*/
public function getShoes($role);
}
WeddingRole是原本的元素類別 (Element)。
WeddingType則是原本元素類別中的操作,會成為我們的訪問者類別 (Visitor)。
根據傳入的元素類別 (Element),而有對應的行為。
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
class BrideGroom implements WeddingRole
{
/**
* @var string
*/
public $name = 'BrideGroom';
/**
* @param WeddingType $weddingType
*/
public function getClothes($weddingType)
{
$weddingType->getClothes($this);
}
/**
* @param WeddingType $weddingType
*/
public function getShoes($weddingType)
{
$weddingType->getShoes($this);
}
}
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
class Bride implements WeddingRole
{
/**
* @var string
*/
public $name = 'Bride';
/**
* @param WeddingType $weddingType
*/
public function getClothes($weddingType)
{
$weddingType->getClothes($this);
}
/**
* @param WeddingType $weddingType
*/
public function getShoes($weddingType)
{
$weddingType->getShoes($this);
}
}
BrideGroom與Bride會由客戶端將WeddingType傳入(第一次分派)
之後再將自己傳給WeddingType (第二次分派)。
<?php
namespace App\VisitorPattern\Wedding\Type;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
class ChineseWedding implements WeddingType
{
/**
* @param WeddingRole $role
*/
public function getClothes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:中式囍袍\n";
break;
case 'Bride':
echo
"新娘:龍鳳褂\n";
break;
}
}
/**
* @param WeddingRole $role
*/
public function getShoes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:黑色秀禾鞋\n";
break;
case 'Bride':
echo
"新娘:紅色秀禾鞋\n";
break;
}
}
}
<?php
namespace App\VisitorPattern\Wedding\Type;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
class JapaneseWedding implements WeddingType
{
/**
* @param WeddingRole $role
*/
public function getClothes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:繡有家紋的和服\n";
break;
case 'Bride':
echo
"新娘:純潔的白無垢\n";
break;
}
}
/**
* @param WeddingRole $role
*/
public function getShoes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:雪駄\n";
break;
case 'Bride':
echo
"新娘:草履\n";
break;
}
}
}
各國婚禮會根據傳入婚禮角色得不同,而有不同的行為。
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use ReflectionClass;
class WeddingTypeFactory
{
/**
* @param string $weddingType
* @return WeddingType
*/
public function create($weddingType)
{
$namespace = 'App\VisitorPattern\Wedding\Type';
$className = $weddingType . 'Wedding';
$reflector = new ReflectionClass($namespace . '\\' . $className);
return $reflector->newInstance();
}
}
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
class Composite
{
/**
* @var WeddingRole[]
*/
protected $children = [];
/**
* @param WeddingRole $role
* @return void
*/
public function add(WeddingRole $role)
{
$this->children[$role->name] = $role;
}
/**
* @param WeddingRole $component
* @return void
*/
public function remove(WeddingRole $role)
{
unset($this->children[$role->name]);
}
/**
* @param WeddingType $weddingType
* @return void
*/
public function display(WeddingType $weddingType)
{
foreach ($this->children as $child) {
$child->getClothes($weddingType);
$child->getShoes($weddingType);
}
}
}
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use App\VisitorPattern\Wedding\WeddingTypeFactory;
use App\VisitorPattern\Wedding\Composite;
use App\VisitorPattern\Wedding\BrideGroom;
use App\VisitorPattern\Wedding\Bride;
class Program
{
/**
* @var WeddingTypeFactory
*/
protected $weddingTypeFactory;
public function __construct()
{
$this->weddingTypeFactory = new WeddingTypeFactory();
}
/**
* @param string $weddingType
*/
public function getWedding($weddingType)
{
$weddingType = $this->createWeddingType($weddingType);
$composite = new Composite();
$brideGroom = new BrideGroom();
$bride = new Bride();
$composite->add($brideGroom);
$composite->add($bride);
$composite->display($weddingType);
}
/**
* @param string $weddingType
* @return WeddingType
*/
private function createWeddingType($weddingType)
{
return $this->weddingTypeFactory->create($weddingType);
}
}
[單一職責原則]
我們將 婚禮角色(資料結構) 與 婚禮類型(操作) 視作兩種不同的職責。
[開放封閉原則]
新增/修改婚禮類型時,不會修改到所有的程式碼。
[介面隔離原則]
婚禮角色介面:會根據客戶端傳入的婚禮類型,再將自己傳入後,完成行為。
婚禮類型介面:會根據傳入的婚禮角色,完成行為。
[依賴反轉原則]
依賴於抽象的婚禮角色介面與婚禮類型介面。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:揉合許多模式的範例。